package cn.seaboot.admin.quartz.smart;

import cn.seaboot.admin.logger.bean.LogLevel;
import cn.seaboot.admin.mvc.SpringContext;
import cn.seaboot.admin.quartz.bean.ScheduleJob;
import cn.seaboot.admin.quartz.bean.ScheduleLog;
import cn.seaboot.admin.quartz.service.ScheduleJobService;
import cn.seaboot.admin.quartz.service.ScheduleLogService;
import cn.seaboot.commons.core.Asserts;
import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.exception.BizException;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;

/**
 * 基础任务
 * <p>
 * 在定时任务的基础上，增加了日志记录的功能。
 * <p>
 * 日志的记录方式：任务开始执行的时候，新增一条日志，任务执行结果，直接改动日志数据。
 * <p>
 * 因为更新数据库的频次较高，比较适合耗时的定时任务
 *
 * @author Mr.css
 * @version 2019/12/7 10:19
 */
public abstract class BaseJob implements Job, JobLogger {
    private final Logger logger = LoggerFactory.getLogger(BaseJob.class);

    protected ScheduleLogService scheduleLogService;

    protected ScheduleJobService scheduleJobService;

    @Override
    public void execute(JobExecutionContext ctx) {
        scheduleLogService = SpringContext.getBean(ScheduleLogService.class);
        Asserts.notNull(scheduleLogService, "ScheduleLogService is undefined!");
        scheduleJobService = SpringContext.getBean(ScheduleJobService.class);
        Asserts.notNull(scheduleJobService, "ScheduleJobService is undefined!");

        // 获取持久化数据
        ScheduleJob job = this.queryJob(ctx);

        // 如果有必要，可以增加状态校验逻辑，避免重复执行

        // exchange schedule job's status
        scheduleJobService.modifyJobStatus(job.getId(), JobStatus.RUN.value());

        // gen log instance
        ScheduleLog scheduleLog = this.beforeJobStart(job);
        try {
            // write started log
            scheduleLogService.insert(scheduleLog);

            // execute job
            int row = this.handler(job, scheduleLog);

            // write succeed log
            this.onJobSucceed(job, scheduleLog, row);
        } catch (Exception e) {
            // write failed log
            this.onJobFailed(job, scheduleLog, e);
        } finally {
            // reset schedule job's status
            scheduleJobService.modifyJobStatus(job.getId(), JobStatus.IDLE.value());
        }
    }

    /**
     * 查找任务实体
     *
     * @param ctx 上下文
     * @return 任务实体
     */
    private ScheduleJob queryJob(JobExecutionContext ctx) {
        JobKey jk = ctx.getJobDetail().getKey();
        TriggerKey tk = ctx.getTrigger().getKey();

        ScheduleJob job = scheduleJobService.selectByName(jk.getGroup(), jk.getName(), tk.getGroup(), tk.getName());
        Asserts.notNull(job, "job is not found: {} - {} ", jk, tk);

        // task is disabled
        if (job.getDisabled()) {
            throw new BizException("job is disabled" + job);
        }

        // job busy
        if (JobStatus.RUN == JobStatus.valueOf(job.getStatus())) {
            throw new BizException("job is running：" + job);
        }
        return job;
    }

    /**
     * 任务启动之前
     *
     * @param job 任务
     * @return 日志实体
     */
    @Override
    public ScheduleLog beforeJobStart(ScheduleJob job) {
        ScheduleLog log = new ScheduleLog();
        log.setJobId(job.getId());

        log.setEffectRows(0);

        log.setJobType(JobType.DEFAULT.value());
        log.setLogLevel(LogLevel.DEBUG.value());
        log.setJobStatus(JobResult.RUNNING.value());

        log.setGmtStart(LocalDateTime.now());
        log.setGmtCreate(log.getGmtStart());
        return log;
    }

    /**
     * 任务执行异常日志
     *
     * @param job 任务
     * @param log 日志对象实体
     * @param e   异常输出
     */
    @Override
    public void onJobFailed(ScheduleJob job, ScheduleLog log, Exception e) {
        logger.error("job execute failed: ", e);

        log.setGmtEnd(LocalDateTime.now());

        log.setLogLevel(LogLevel.ERROR.value());
        log.setJobStatus(JobResult.ITEM_ERROR.value());
        log.setErrorMessage(CommonUtils.getStackTraceString(e));

        scheduleLogService.updateById(log);
    }

    /**
     * 任务完整结束之后，记录结束时间，将状态标志为成功
     *
     * @param job 任务
     * @param log 日志对象实体
     */
    @Override
    public void onJobSucceed(ScheduleJob job, ScheduleLog log, int row) {
        log.setEffectRows(row);
        log.setGmtEnd(LocalDateTime.now());
        log.setJobStatus(JobResult.SUCCEED.value());
        scheduleLogService.updateById(log);
    }

    /**
     * 处理计划任务
     *
     * @param job 任务实体
     * @param log 日志实体，如果有必要，业务上可以记录日志 id，用于业务溯源
     * @return effect-rows
     */
    public abstract int handler(ScheduleJob job, ScheduleLog log);
}